6장. 함께 태어난 부품들

출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 제이펍 2026) | 원서: Balancing Coupling in Software Design (Manning) · 입문판·PDF 재구성

코드는 분위기만 — enum·interface·get; 같은 말은 몰라도 됩니다. 표의 '비유'와 '위험'만 봐도 충분해요.

5장에서는 연결(결합)을 6등급으로 줄 세웠다.

그건 1960~70년대 사람들이 만든 모델이다.

그런데 그 모델은 옛날 프로그래밍(절차형)을 기준으로 만들어졌다.

객체·클래스가 흔해진 1990년대에, 더 잘게 쪼개 보는 새 모델이 나왔다.

그 이름이 공생성이다.

뜻은 "함께 태어남"이다.

이 장은 공생성이 무엇이고, 그 9가지 종류를 약한 것부터 강한 것까지 훑는다.


0. 이 장의 새 단어 (3개)

0장에 없던 말은 딱 3개다.

나머지 어려운 말(결합·상위/하위·공유 지식·인터페이스·변경 전파·복잡한 상호작용·모듈 결합 6수준)은 전부 0장과 5장에 있다.

막히면 0장으로 돌아가면 된다.


공생성 (Connascence)

한 문장 뜻 — 두 부품이 함께 태어나 얽혀 있어, 한쪽을 바꾸면 다른 쪽도 같이 바꿔야 하는 관계.

일상비유 — 쌍둥이. 한 명이 독감에 걸리면 다른 한 명도 검사받아야 한다. 따로 사는 친구라면 그럴 필요 없다.

한 줄 예 —

static dynamic Greet(string name) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    Console.WriteLine($"Hi {name}"); // 이 줄도 같이 바꿔야 함 = 공생성
}

정적 공생성 vs 동적 공생성 (Static vs Dynamic Connascence)

한 문장 뜻 — 코드를 읽기만 해도 보이는 얽힘이 정적, 프로그램을 실제로 돌려봐야 드러나는 얽힘이 동적.

일상비유 — 설계도만 봐도 아는 문제(정적) vs 실제로 작동시켜 봐야 아는 문제(동적). 자동차로 치면, 도면상 나사 규격 불일치(정적) vs 달려 봐야 아는 떨림(동적).

한 줄 예 —

Greet("world"); // 이름이 맞나? 코드만 봐도 앎(정적)
open(); commit(); // 순서가 맞나? 돌려봐야 앎(동적)

마법 값 (Magic Value)

한 문장 뜻 — 코드에 그냥 박힌 숫자·문자열인데, 그게 무슨 뜻인지 코드만 봐서는 알 수 없는 값.

일상비유 — 가게 메모에 "재고 7"이라고만 적혀 있다. 7이 박스 수인지 개수인지 코드(약속)를 아는 사람만 안다.

한 줄 예 —

set_status(case, 7); // 7 이 뭘 뜻하지? 코드만으론 모름 = 마법 값

(귀납 도입) 이런 적 있죠?

함수 이름 하나 바꿨더니, 그 이름을 부르던 다른 줄이 줄줄이 깨진 적 있죠?

static dynamic Greet(string name) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var message = $"Hi {name}"; // 여기도 nm 으로 바꿔야 하고
    Console.WriteLine(message); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}

name 하나 고치면 정확히 그 이름을 쓰는 줄들이 함께 깨진다.

이 줄들은 함께 태어난 사이다.

한쪽이 변하면 다른 쪽도 따라 변해야 한다.

이게 바로 공생성이다.

5장의 모듈 결합이 "어떤 방식으로 연결됐나"(전역 변수냐, 플래그냐)를 물었다면,

공생성은 "어떤 종류의 지식을 함께 알아야 하나"(이름이냐, 순서냐, 같은 물건이냐)를 더 잘게 물어본다.

5장이 매운맛 6단계라면, 공생성은 같은 맛을 9칸으로 더 잘게 나눈 자다.


이 장에서 딱 3가지만

이 장에서 딱 3가지만

  1. 공생성 = 함께 태어남 — 한쪽을 바꾸면 다른 쪽도 바꿔야 하는 관계. 5장보다 잘게 쪼갠 자다.
  2. 두 줄, 아홉 칸 — 코드만 봐도 보이는 정적 5종(이름·유형·의미·알고리즘·위치), 돌려봐야 보이는 동적 4종(실행·타이밍·값·동일). 아래로 갈수록 강하다.
  3. 여럿 겹치면 가장 센 게 대표 — 한 인터페이스에 여러 공생성이 겹치면, 그 중 가장 강한 수준이 전체를 대표한다.

큰 그림 하나: 약한 공생성일수록 인터페이스가 명시적(누가 봐도 보임)이고, 강할수록 암묵적(약속을 알아야 보임)이다.


개념 1. 공생성이란? — 함께 태어난 부품

망가지는 장면

"이 모듈만 살짝 바꿨는데, 왜 저 모듈까지 손대야 하지?"

둘이 따로 사는 줄 알았는데, 알고 보니 같이 태어난 사이였다.

한쪽을 건드리면 다른 쪽도 반드시 따라 바꿔야 했던 것이다.

일상비유

쌍둥이.

한 아이가 독감에 걸리면 부모는 다른 아이도 검사한다.

따로 사는 친구라면 "나만 아프면 그만"이지만, 함께 태어난 쌍둥이는 한 명의 변화가 다른 명의 검토를 부른다.

비유 코드 위험
쌍둥이(함께 태어남) name 바꾸면 부르는 줄도 바꿈 한쪽 변경이 다른 쪽 변경을 부름
따로 사는 친구 서로 호출 없음 영향 없음 — 공생성 없음

정의 — 한 모듈을 바꿀 때 다른 모듈도 바꿔야 하거나 최소한 세심히 검토해야 한다면, 둘은 공생성 관계다.

또 두 모듈을 동시에 바꿔야 할 요구사항 변경이 예상되는 경우에도 공생성으로 본다.

예시 1 (worked — 완성예): 이름이 묶은 두 줄

static dynamic Greet(string name) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    Console.WriteLine($"Hi {name}"); // name 사용 — 위 줄과 함께 태어남
}

namenm으로 바꾸면 반드시 두 줄을 같이 바꿔야 한다.

따로 못 바꾼다. 함께 태어났으니까.

예시 2 (worked): 따로 사는 두 함수

static dynamic Add(dynamic a, dynamic b) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return a + b; // 계산 결과를 호출자에게 돌려줍니다.
}
static dynamic Now() // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return time(); // 계산 결과를 호출자에게 돌려줍니다.
}

add를 고쳐도 now는 멀쩡하다.

둘은 함께 태어난 사이가 아니다 → 공생성 없음.

예시 3 (부분완성 — 빈칸 채우기)

아래 두 쌍 중 공생성이 있는 쪽은?

// (A)
static dynamic F(dynamic x) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return g(x); // 계산 결과를 호출자에게 돌려줍니다.
}

// (B)
static dynamic P() // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return 1; // 계산 결과를 호출자에게 돌려줍니다.
}
static dynamic Q() // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return 2; // 계산 결과를 호출자에게 돌려줍니다.
}

정답은 (A).

fg라는 이름을 알아야 한다 — g 이름을 바꾸면 f도 깨진다(함께 태어남).

(B)는 서로 모르니 공생성 없음.

예시 4 (독립적용): 직접 판단

두 서비스가 같은 메서드 이름 process_payment에 합의해야 통신된다.

공생성이 있을까?

있다.

한쪽이 이름을 바꾸면 다른 쪽도 바꿔야 하니까 — 함께 태어난 사이다.

미니 시나리오

"내 코드만 고쳤는데 왜 옆 모듈이 깨지지?"

→ 그 모듈과 내 코드가 같은 이름/순서/값을 공유하고 있었던 것.

→ 그게 공생성. 어떤 종류의 지식을 공유하는지가 다음 절의 주제다.

단순 규칙 — 한쪽을 바꾸면 다른 쪽도 바꿔야 하면 공생성. 공유하는 지식이 적을수록 약한(안전한) 공생성이다.


개념 2. 정적 공생성 — 코드만 읽어도 보인다

정적 공생성은 코드를 읽기만 해도 찾아낼 수 있는 얽힘이다.

돌려볼 필요가 없다. 도구로 자동 검사도 된다.

공유하는 지식이 적은 것부터 많은 것 순서로 5칸이 있다.

이름 → 유형 → 의미 → 알고리즘 → 위치

왼쪽일수록 약하고 명시적, 오른쪽일수록 강하고 암묵적이다.

하나씩 본다.


2-1. 이름 공생성 — 같은 이름표를 달아야 한다

망가지는 장면

변수 이름 하나 바꿨더니, 그 이름을 쓰던 줄이 다 빨갛게 떴다.

일상비유

아파트 우편함 이름표.

택배기사는 이름표를 보고 문을 두드린다.

내가 이름을 바꾸면 택배기사에게도 새 이름표를 알려야 한다. 안 그러면 택배가 영영 안 온다.

비유 코드 위험
우편함 이름표 Greet("world")void Greet(string name) 이름 하나 바꾸면 양쪽이 깨짐
이름표 안 맞음 greet 부르는데 함수는 hello 통합이 작동 안 함

정의 — 연결된 부품이 같은 이름으로 같은 것을 가리켜야 하는 관계.

변수명·메서드명·서비스 엔드포인트 이름이 다 여기 해당한다.

예시 1 (worked): 세 이름이 묶인 코드

static dynamic Greet(string name) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var message = $"Hi {name}"; // 이름 3: message
    Console.WriteLine(message); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
Greet("world"); // greet 이름이 위와 맞아야 함

greet·name·message 세 이름 중 하나만 바꿔도 정확히 두 줄이 같이 영향받는다.

예시 2 (worked): 웹 서비스도 마찬가지

// 호출 쪽
// http.PostAsJson("/charge", new { Amount = 100 }); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.
// 서버 쪽이 받는 이름도 "amount" 여야 함 — 다르면 안 통함

엔드포인트 이름(/charge)과 인수 이름(amount)을 양쪽이 같이 알아야 한다.

예시 3 (부분완성): 이름 공생성 찾기

var total = price * qty; // (A)
Greet("world"); // (B) — 함수 이름 greet 를 공유

이름 공생성이 뚜렷한 쪽은 (B).

greet라는 이름을 호출부와 정의부가 같이 알아야 한다.

((A)도 price·qty 이름 공생성이 있지만, 같은 함수 안이라 좁다.)

예시 4 (독립적용)

두 클래스가 공개 메서드 save로 통신한다. savestore로 바꾸면?

→ 부르는 쪽도 다 바꿔야 한다. 이름 공생성이다.

왜 가장 약한가

  • 가장 명시적이다. 이름이 안 맞으면 바로 보인다(빨간 줄).
  • 현대 IDE의 "리네임 리팩토링" 한 번으로 한꺼번에 고쳐진다.
  • 그래도 불가피한 결합이다 — 이름이 없으면 서로를 가리킬 수조차 없다.

단순 규칙 — 같은 이름만 맞추면 되는 게 이름 공생성. 가장 약하고, IDE로 관리 가능.


2-2. 유형 공생성 — 같은 형식의 서류를 써야 한다

망가지는 장면

이름은 맞게 적었는데, 숫자 칸에 글자를 넣어서 서류가 반려됐다.

일상비유

해외 취업 서류는 영어로 제출해야 한다.

이름을 맞게 써도 언어(형식)가 틀리면 무효다.

한국어로 완벽하게 쓴 자소서는 받아주지 않는다.

비유 코드 위험
영어 서류 형식 Greet("world") (문자열) 숫자 넣으면 거부됨
형식 틀림 Greet(42) (숫자) 컴파일 오류 또는 실행 중 실패

정의 — 이름뿐 아니라 값의 유형(타입)도 같아야 하는 관계.

강타입 언어에서 또렷이 보이고, C# 같은 동적 언어에서도 암묵적 타입 가정은 남는다.

예시 1 (worked): 타입이 맞아야 통과

static dynamic Greet(string name) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    Console.WriteLine($"Hi {name}"); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
Greet("world"); // 문자열 — OK

이름(greet)을 아는 것만으로는 부족하다.

name문자열을 넘긴다는 가정까지 양쪽이 공유해야 한다.

예시 2 (worked): 타입 어기면 터짐 (before)

// 잘못된 예: 숫자를 넘김
Greet(42); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
// C# 은 일단 받지만, name 으로 문자열 연산을 하다가 실행 중에 깨질 수 있음

코드는 유효해 보여도 돌려보면 실패할 수 있다.

다른 유형이 들어오면 그렇다.

예시 3 (부분완성): 같이 따라오는 공생성

이름 공생성과 유형 공생성은 보통 함께 나타난다.

add_days(date, 3); // date 는 날짜 타입, 3 은 숫자 타입

add_days라는 이름도 맞아야 하고(이름 공생성),

date날짜 타입, 3숫자 타입이어야 한다(______ 공생성).

빈칸 정답: 유형.

예시 4 (독립적용)

한 함수가 price에 문자열 "100"을 받아 숫자처럼 곱하려다 실패했다.

→ 유형 공생성 위반이다. 양쪽이 숫자라는 형식에 합의했어야 한다.

단순 규칙 — 이름에 더해 타입까지 맞춰야 하면 유형 공생성. 이름 공생성보다 한 단계 강하다.


2-3. 의미 공생성 — 비밀 암호를 둘이 나눠 갖는다

망가지는 장면

코드에 7이라고만 적혀 있다.

7이 뭘 뜻하는지 코드만 봐서는 아무도 모른다.

일상비유

도서관 내부 코드.

장르코드 = 7이 "SF"라고 사서와 서가 담당자가 약속했다.

새 직원이 7의 뜻을 모르면 책을 엉뚱한 곳에 꽂는다.

코드북을 공유 안 하면 아무도 못 푸는 마법 값이다.

비유 코드 위험
코드북 약속(=7) set_status(case, 7) 7 의 뜻을 양쪽이 알아야만 작동
코드북 없음 새 직원이 7 을 봄 무슨 뜻인지 몰라 실수

정의 — 두 부품이 특정 값에 특별한 의미를 부여하기로 암묵 합의한 관계.

그 의미는 코드만 봐서는 알 수 없다. 컴파일러도 못 잡는다.

예시 1 (worked): 마법 값 7 (before)

static dynamic ProcessEmail(dynamic msg, dynamic case_id) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var case = repo.Load(case_id); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
    // case.AppendResponse(msg.Body, newStatus: 7); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.
}

7이 "재오픈"을 뜻한다고 두 모듈이 암묵적으로 약속했다.

코드엔 그 약속이 안 적혀 있다.

예시 2 (worked): 열거형으로 격상 (after)

from enum import Enum; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

public class Status : Enum // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    var Open = 0; Reopened = 6; // 이름을 붙여 둠

}
static dynamic ProcessEmail(dynamic msg, dynamic case_id) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var case = repo.Load(case_id); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
    // case.AppendResponse(msg.Body, newStatus: Status.Reopened); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.
}

7Status.Reopened로 바꾸니 코드만 봐도 뜻을 안다.

의미 공생성이 이름+유형 공생성으로 내려갔다(더 약해졌다).

예시 3 (부분완성): 마법 값 찾기

charge(card, 3); // (A)
// Charge(card, mode: "full"); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.

마법 값이 더 심한 쪽은 (A).

3이 뭘 뜻하는지 코드만으론 모른다.

((B)도 "full" 의미를 공유하지만, 글자라 그나마 짐작은 된다.)

예시 4 (독립적용)

응답 코드 2가 "배송됨"을 뜻한다고 두 모듈이 약속했다.

→ 의미 공생성이다. 열거형(Status.Shipped)으로 낮출 수 있다.

단순 규칙 — 숫자·글자에 숨은 뜻을 둘이 나눠 알아야 하면 의미 공생성. 열거형·상수로 의미를 코드에 적어 낮춘다.


2-4. 알고리즘 공생성 — 암호 해독법을 같이 알아야 한다

망가지는 장면

한쪽은 데이터를 한 방식으로 잠그고, 다른 쪽은 다른 방식으로 풀려 한다.

값은 오갔는데 해독이 안 된다.

일상비유

스파이 영화의 암호 통신.

발신자와 수신자가 같은 암호 방식을 써야 메시지가 풀린다.

발신자가 카이사르 암호로 잠그고 수신자가 다른 방식으로 풀면 — 의미 없는 글자만 나온다.

비유 코드 위험
같은 암호 방식 양쪽 다 md5 로 검사 방식 합의되면 통함
다른 암호 방식 한쪽 md5, 다른쪽 sha256 항상 불일치 — 통합 깨짐

정의 — 두 부품이 교환하는 값을 이해하려고 같은 알고리즘을 쓰기로 합의한 관계.

암호화·해시·체크섬 방식이 전형적인 예다.

예시 1 (worked): 체크섬 합의 (before)

// 업로드 쪽
var checksum = calculate_md5(data); // MD5 로 계산
storage.Upload(data, checksum); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

// 검증 쪽
static dynamic Verify(dynamic data, dynamic checksum) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return calculate_sha256(data) == checksum; // SHA-256 → 항상 실패!
}

값은 잘 넘어갔다. 그런데 한쪽은 MD5, 다른쪽은 SHA-256이다.

영영 안 맞는다.

예시 2 (worked): 알고리즘 단일화 (after)

var HASH = "md5"; // 한 곳에만 적어 둠 (단일 출처)

static dynamic Make(dynamic data) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return calculate(data, HASH); // 계산 결과를 호출자에게 돌려줍니다.
}
static dynamic Verify(dynamic data, dynamic c) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return calculate(data, HASH) == c; // 계산 결과를 호출자에게 돌려줍니다.
}

알고리즘을 한 곳에 적고 양쪽이 그걸 본다.

이제 항상 맞는다.

예시 3 (부분완성): 흔한 오해 바로잡기

알고리즘 공생성은 코드가 중복됐냐의 문제가 아니다.

알고리즘이 외부 라이브러리에 한 번만 있든, 양쪽에 복사돼 있든 — 중요한 건 같은 방식에 합의했냐다.

그래서 척도에서 비교적 ___ 순위(9점 만점에 4점)다.

빈칸 정답: 낮은.

예시 4 (독립적용)

두 서비스가 JSON을 같은 직렬화 방식으로 주고받기로 약속했다.

→ 알고리즘 공생성이다. 같은 방식에 합의해야 서로를 이해한다.

단순 규칙 — 값을 이해하려면 같은 처리 방식에 합의해야 하면 알고리즘 공생성. 방식을 한 곳에 적어 단일화한다.


2-5. 위치 공생성 — 줄 서는 순서가 의미를 결정한다

망가지는 장면

값은 다 맞는데 순서를 바꿔 넣었더니, 보낸 사람과 받는 사람이 뒤바뀌었다.

오류 메시지도 없이 조용히 틀렸다.

일상비유

티켓 양식에 "이름, 좌석번호, 공연일" 순으로 적어야 한다.

순서를 바꿔 "공연일, 이름, 좌석번호"로 적으면, 시스템이 공연일을 이름으로 읽는다.

내용이 다 맞아도 위치가 틀리면 오류다.

비유 코드 위험
양식 항목 순서 data[0]=보내는이, data[1]=받는이 순서 헷갈리면 조용히 틀림
이름표 붙은 칸 msg.from_addr, msg.to_addr 안전 — 이름이 의미 결정

정의 — 여러 부품이 요소의 순서에 합의해야 하는 관계.

배열 인덱스, 같은 타입 인수 여럿, 이름 없는 튜플이 전형적인 예다.

예시 1 (worked): 배열 순서 (before)

static dynamic SendEmail(dynamic data) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var from_addr = data[0]; // 0번이 보내는이
    var to_addr = data[1]; // 1번이 받는이
    var subject = data[2]; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
    var body = data[3]; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
}

data를 만드는 쪽이 순서를 정확히 알아야 한다.

0번과 1번을 바꿔 넣으면 보낸 사람과 받는 사람이 뒤바뀐다 — 오류 없이.

예시 2 (worked): 이름 없는 튜플 반환 (before)

static dynamic GetTimes() // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return (DateTime.Now, DateTime.UtcNow); // 어느 게 먼저?

}
// var (local, utc) = GetTimes(); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.

반환값 두 개 중 어느 게 지역 시간인지 순서로만 정해진다.

순서를 잘못 받으면 지역/UTC가 뒤바뀐다.

예시 3 (부분완성): 이름표로 낮추기 (after)

위치 공생성은 이름을 붙이면 이름 공생성으로 내려간다.

from dataclasses import dataclass; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

@dataclass; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
public class Email // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public string FromAddr { get; init; } // 외부에 보여 줄 읽기용 값입니다.
    public string ToAddr { get; init; } // 외부에 보여 줄 읽기용 값입니다.

}
static dynamic SendEmail(dynamic msg: Email) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    /* 구현 생략 */ // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}

빈칸 정답: 이름.

이름표를 붙이니 순서를 바꿔도 안전하다.

예시 4 (독립적용)

함수가 (성공여부, 잔액, 거래ID) 순서로 튜플을 반환한다.

→ 위치 공생성이다. 호출자가 순서를 외워야 한다. 이름 있는 객체로 낮출 수 있다.

왜 정적 중 가장 강한가

  • 가장 암묵적이다. 순서가 틀려도 빨간 줄이 안 뜬다. 조용히 틀린 결과만 나온다.
  • 겉보기엔 이름 공생성과 비슷해 보이지만, 하나는 인터페이스를 명시적으로, 다른 하나는 암묵적으로 만든다.

단순 규칙 — 순서가 의미를 정하면 위치 공생성. 정적 중 가장 강하다. 이름표(named 인수·데이터 클래스)로 낮춘다.


개념 3. 동적 공생성 — 돌려봐야 보인다

여기서부터는 코드를 아무리 읽어도 안 보인다.

실제로 돌려봐야 드러나는 얽힘이다.

동적 공생성은 정적 공생성 전체보다 더 강한 종속을 만든다.

약한 것부터 4칸이 있다.

실행 → 타이밍 → 값 → 동일


3-1. 실행 공생성 — 입장 순서를 지켜야 한다

망가지는 장면

순서를 어겨 호출했더니, 코드는 멀쩡해 보이는데 돌리니까 터졌다.

일상비유

오케스트라 합주.

타악기 먼저, 그다음 현악기, 마지막에 관악기가 들어온다.

관악기가 현악기보다 먼저 나오면 연주가 망가진다.

순서 자체가 음악의 일부다.

비유 코드 위험
입장 순서 지킴 open()commit() 순서 맞으면 작동
순서 어김 commit()open() 돌리면 런타임 오류

정의 — 부품들이 특정 순서로 실행되어야 하는 관계.

정적의 위치 공생성에 대응하는 동적 공생성이다(위치=코드 순서, 실행=실행 순서).

예시 1 (worked): DB 트랜잭션 순서

conn.OpenConnection(); // 1번 먼저
conn.BeginTransaction(tx_id); // 2번 — open 후에만
conn.ExecuteQuery("SELECT /* 구현 생략 */"); // 3번 — begin 후에만
conn.Commit(tx_id); // 4번 — query 후에
conn.CloseConnection(); // 5번 마지막

이 순서를 어기면 작동 안 한다.

open 전에 commit하면 돌릴 때 터진다.

예시 2 (worked): 순서 위반 (before)

conn.Commit(tx_id); // 오류! 아직 open 도 안 했는데 커밋
conn.OpenConnection(); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

코드 줄 자체는 빨간 줄이 없다.

돌려봐야 깨진다. 그래서 동적이다.

예시 3 (부분완성): 정적이냐 동적이냐

이름·유형 공생성은 코드만 봐도 보인다 → 정적.

실행 공생성은 코드만 봐선 안 보이고 _____ 봐야 보인다 → 동적.

빈칸 정답: 돌려.

예시 4 (독립적용)

파일을 열기 전에 읽으려 하면 실행 중 오류가 난다.

→ 실행 공생성이다. "열기 → 읽기" 순서가 강제된다.

단순 규칙 — 특정 순서로 실행해야 하면 실행 공생성. 코드만 봐선 안 보이는 동적 공생성이다.


3-2. 타이밍 공생성 — 정확한 시간 간격이 있다

망가지는 장면

순서는 맞췄는데 시간이 어긋나서 결과가 틀렸다.

일상비유

"오전 10시~12시 배달" 택배.

그 시간에 집에 있어야 받는다. 10시 5분에 나가면 택배가 왔다 돌아간다.

순서뿐 아니라 시간 간격이 중요하다.

비유 코드 위험
배달 시간 맞춤 클록 한 번만 조회 간격 안정 → 정확
시간 어긋남 클록 두 번 조회 그 사이 분이 바뀌면 틀림

정의 — 순서뿐 아니라 두 실행 사이의 시간 간격에도 의존하는 관계.

DB 연결 타임아웃, 시스템 시계 이중 조회가 전형적인 예다.

예시 1 (worked): 시계 두 번 조회 (before)

static dynamic GetTime() // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var hour = DateTime.Now.Hour; // 첫 번째 조회
    var minute = DateTime.Now.Minute; // 두 번째 조회 — 그 사이 시간 흐름
    return (hour, minute); // 결과를 호출자에게 돌려줍니다.
}

23시 59분에 첫 줄에서 hour=23을 읽고,

그 사이 분이 바뀌면 둘째 줄에서 minute=0을 읽는다 → 23시 00분이라는 틀린 결과.

예시 2 (worked): 한 번만 조회 (after)

static dynamic GetTime() // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    var now = DateTime.Now; // 한 번만 — 같은 순간을 박제
    return (now.Hour, now.Minute); // 결과를 호출자에게 돌려줍니다.
}

시계를 한 번만 보고 그 값을 재사용한다.

두 값이 같은 순간이라 어긋날 수 없다.

예시 3 (부분완성): 제거되나 안 되나

구현 부주의로 생긴 타이밍 의존(시계 이중 조회)은 ___ 있다.

빈칸 정답: 낮출 수.

하지만 "연결 후 30초 뒤 타임아웃"처럼 요구사항 자체가 시간 간격을 정하면, 리팩토링해도 그 30초 의존은 못 없앤다.

예시 4 (독립적용)

자동차 문이 잠금 해제 후 일정 시간 안 열리면 자동으로 다시 잠긴다.

→ 타이밍 공생성이다. "해제"와 "재잠금" 사이 시간 간격이 동작을 정한다.

단순 규칙 — 두 동작 사이 시간 간격이 결과를 정하면 타이밍 공생성. 부주의는 낮추고, 요구사항인 건 관리한다.


3-3. 값 공생성 — 여러 값을 혼자 바꿀 수 없다

망가지는 장면

한 값만 바꿨더니, 같이 바꿔야 할 다른 값이 그대로라 말이 안 되는 상태가 됐다.

일상비유

삼각형 세 변.

한 변을 늘리면 삼각형 모양을 유지하려고 다른 변도 같이 바뀌어야 한다.

혹은 가게 장부 — 판매 수를 올리면 재고 수를 그만큼 줄여야 한다.

둘 다 안 바꾸면 장부가 틀린다.

비유 코드 위험
삼각형 세 변 A, B, C 를 함께 바꿈 하나만 바꾸면 모양이 깨짐
판매-재고 둘을 한 번에 갱신 따로 바꾸면 장부 불일치

정의 — 여러 값이 동시에 바뀌어야 올바른 상태를 유지하는 강한 관계.

산술 제약이나 비즈니스 규칙이 이 관계를 만든다. 하나만 바꾸면 말이 안 되는 상태가 된다.

예시 1 (worked): 삼각형 제약

public class Triangle // 유효한 삼각형이라는 제약을 객체 안에 가두는 예입니다.
{
    public dynamic SetEdges(dynamic a, dynamic b, dynamic c) // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        if (a >= b + c || b >= a + c || c >= a + b) // 조건이 참일 때만 아래 결정을 실행합니다.
        {
            throw new ArgumentException("삼각형 조건 위반"); // 잘못된 상태를 객체 안으로 들이지 않습니다.
        }
        // this.a = a; this.b = b; this.c = c; // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.
}
}

한 변만 바꿀 수 없다.

세 변을 한 메서드에서 함께 바꿔 제약을 지킨다.

예시 2 (worked): 비즈니스 규칙

public class Customer // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public dynamic ClearVerification() // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        this.IsVerified = false; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
        this.PriorityShipping = false; // 검증 풀리면 우선배송도 같이 꺼짐
}
}

검증을 취소하면 우선 배송 자격도 함께 꺼야 일관성이 유지된다.

is_verifiedpriority_shipping은 값 공생성으로 묶여 있다.

예시 3 (부분완성): 해결책 떠올리기

값 공생성이 있는 값들은 따로 두지 말고, 한 _____ 에 묶어 함께 바꾸게 강제하면 안전하다.

빈칸 정답: 메서드.

예시 4 (독립적용)

주문 합계와 세금이 항상 "세금 = 합계 × 0.1" 관계를 지켜야 한다.

→ 값 공생성이다. 합계가 바뀌면 세금도 함께 바뀌어야 한다.

단순 규칙 — 여러 값이 동시에 바뀌어야 일관성이 유지되면 값 공생성. 한 메서드에 묶어 원자적으로 바꾼다.


3-4. 동일 공생성 — 정확히 같은 물건을 써야 한다

망가지는 장면

둘이 "같은 걸 쓴다"고 믿었는데, 사실은 각자 다른 복제본을 쓰고 있어서 따로 놀았다.

일상비유

쉐어하우스에 냄비가 딱 한 개.

두 사람이 공동 냄비를 함께 써야 한다면, 한 사람이 냄비를 치우는 순간 다른 사람 요리도 즉시 영향받는다.

각자 다른 냄비를 쓰면 "같은 냄비"라는 약속이 깨진다.

비유 코드 위험
공동 냄비 1개 둘 다 같은 pool 참조 정확히 같은 물건이라 조율됨
각자 다른 냄비 각자 Pool() 새로 만듦 따로 놀아 일관성 깨짐

정의 — 두 부품이 올바르게 동작하려면 세 번째 물건의 정확히 같은 인스턴스(같은 한 개)를 함께 참조해야 하는 가장 강한 관계.

공유 물건의 모든 변경이 양쪽에 즉시 보인다.

예시 1 (worked): 각자 만들면 따로 논다 (before)

public class ModuleA // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public ModuleA() // 생성자: 이 객체가 어떤 협력 객체를 기억할지 정합니다.
    {
        this.Pool = new ConnectionPool(); // 자기만의 풀

}
}
public class ModuleB // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public ModuleB() // 생성자: 이 객체가 어떤 협력 객체를 기억할지 정합니다.
    {
        this.Pool = new ConnectionPool(); // 또 다른 풀 — 연결 두 배 소비
}
}

"같은 풀을 쓴다"고 믿지만 사실은 다른 두 개다.

조율도 안 되고 자원도 낭비된다.

예시 2 (worked): 같은 하나를 주입 (after)

var shared_pool = new ConnectionPool(max=10); // 딱 하나

var a = new ModuleA(shared_pool); // 같은 풀을 받음
var b = new ModuleB(shared_pool); // 정확히 같은 풀

바깥에서 만든 하나를 양쪽에 넣어 준다.

이제 진짜로 같은 물건을 본다.

예시 3 (부분완성): 가장 강한 이유

동일 공생성은 정확히 같은 한 개에 의존한다.

그 한 개가 바뀌면 양쪽에 ___ 보인다.

빈칸 정답: 즉시.

그래서 9칸 중 가장 강하다.

예시 4 (독립적용)

두 서비스가 같은 DB에 쓰고, 서로의 변경을 즉시 읽기로 가정한다(강한 일관성).

→ 동일 공생성이다. 정확히 같은 DB 상태를 함께 본다.

(단, 메시지 버스로 통신하며 즉시 일관성을 가정 안 하면 동일 공생성이 아닐 수 있다.)

단순 규칙정확히 같은 한 개를 함께 써야 작동하면 동일 공생성. 9칸 중 가장 강하다. 의존성 주입으로 같은 인스턴스를 넘긴다.


개념 4. 여럿 겹치면 가장 센 게 대표

망가지는 장면

한 줄짜리 함수 호출인데, 따져 보니 공생성이 대여섯 개나 겹쳐 있었다.

일상비유

매운맛 1·3·5단계 양념이 한 그릇에 다 들어갔다.

이 그릇은 몇 단계 맵다고 해야 할까?

가장 센 5단계라고 한다. 가장 매운 게 입맛을 지배하니까.

비유 코드 위험
가장 센 양념 기준 여러 공생성 중 최댓값 약한 것만 보면 위험 과소평가

정의 — 한 인터페이스에 여러 공생성이 동시에 있으면, 가장 높은 수준이 전체를 대표한다.

예시 1 (worked): 한 줄에 다섯 겹

// var (result, balance, transactionId) = accounting.ProcessPayment(request); // 여러 값을 한 번에 넣는 C#에서는 개별 대입으로 풀어 쓰는 표현은 C#에서 개별 대입으로 풀어 씁니다.
    var account_id = 'LVG141028',; // 이름·유형 공생성
    var transaction_type = 3,; // 의미 공생성 (3 = 마법 값)
    credit_card='S5hDn/* 구현 생략 */=='; // 알고리즘 공생성 (AES 암호화)
); // 반환 튜플 순서 → 위치 공생성

이 한 줄에 이름·유형·의미·알고리즘·위치 공생성이 다 들어 있다.

예시 2 (worked): 전체 수준 판정

위 다섯 개 중 가장 강한 건 위치 공생성이다.

→ 그래서 이 호출의 전체 공생성은 위치 공생성이다.

가장 약한 이름 공생성만 보고 "약하네" 하면 위험을 과소평가한 것이다.

예시 3 (부분완성): 직접 판정

한 함수에 의미 공생성과 위치 공생성이 같이 있다.

전체 수준은? → _____ 공생성.

빈칸 정답: 위치.

(위치가 의미보다 강하니까.)

예시 4 (독립적용)

이름·유형 공생성만 있는 호출과, 거기에 위치 공생성까지 더해진 호출.

→ 둘째 호출이 더 강하다(위치가 끼었으니). 전체 수준은 위치 공생성.

단순 규칙 — 여러 공생성이 겹치면 가장 센 것이 전체를 대표한다. 약한 것만 보면 안 된다.


공생성 9칸 한 줄 묶음표

종류 핵심 신호 비유
1 이름 (약) 정적 이름 하나 바꾸면 양쪽 깨짐 우편함 이름표
2 유형 정적 타입 안 맞으면 거부 해외 서류 형식
3 의미 정적 마법 값·하드코딩 숫자 도서관 코드북
4 알고리즘 정적 같은 처리 방식 합의 스파이 암호
5 위치 정적 순서가 의미 결정 티켓 양식 순서
6 실행 동적 호출 순서 강제 합주 입장 순서
7 타이밍 동적 시간 간격 의존 배달 시간
8 동적 여러 값 동시 변경 삼각형 세 변
9 동일 (강) 동적 정확히 같은 한 개 공동 냄비

위로 갈수록: 약함 · 명시적 · 코드만 봐도 보임

아래로 갈수록: 강함 · 암묵적 · 돌려봐야 보임

정적 5칸은 코드만 읽어도, 동적 4칸은 실행해야 보인다.


6장 요점 — 코드에서 공생성 찾기

지금은 "이 공생성이 좋냐 나쁘냐"를 판단하는 단계가 아니다.

어떤 종류의 지식을 함께 알아야 하는지 알아보는 단계다.

코드를 볼 때 이렇게 물어보면 된다.

  • 같은 이름/타입을 양쪽이 맞춰야 하나? (이름·유형 공생성)
  • 숫자·글자에 숨은 뜻을 둘이 나눠 아나? (의미 공생성)
  • 순서가 의미를 정하나? (위치·실행 공생성)
  • 여러 값이 같이 바뀌어야 하나, 같은 한 개를 써야 하나? (값·동일 공생성)

여러 개가 겹치면 가장 센 것이 전체를 대표한다.


정리

  • 공생성은 "함께 태어남" — 한쪽을 바꾸면 다른 쪽도 바꿔야 하는 관계. 5장보다 잘게 쪼갠 자다.
  • 정적 5종(이름·유형·의미·알고리즘·위치)은 코드만 봐도, 동적 4종(실행·타이밍·값·동일)은 돌려봐야 보인다. 아래로 갈수록 강하다.
  • 여러 공생성이 겹치면 가장 센 것이 전체를 대표한다.

한 걸음 더 ▸ (지금 몰라도 됨)

5장의 모듈 결합과 6장의 공생성을 한 줄로 합치고 싶을 수 있다. 그런데 둘은 다른 측면을 잰다 — 한쪽은 연결 방식, 한쪽은 공유 지식. 그래서 가장 강한 결합(콘텐츠)이 가장 약한 공생성(이름)에 해당하는 역설도 생긴다. 또 두 모델 다 못 잡는 사각지대(물리적 연결 없이 같은 규칙을 따로 구현)도 있다. 이걸 어떻게 합칠지는 더 깊은 주제라, 지금은 "공생성 9칸을 알아본다" 하나만 들고 가면 된다.


더 해보기

9가지 공생성을 강도(degree)·지역성(locality) 가이드라인과 함께 인터랙티브하게 볼 수 있다: connascence.io (검증 2026-05-21).


다음 7장 예고 — 5장 모듈 결합과 6장 공생성을 하나로 합친 새 모델(통합 강도)을 본다. (지금 몰라도 됩니다 — 7장에서 천천히 풀려요.)


연습문제

  1. 설명. 함께 태어난 부품들의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라.
  2. 구분. 두 개념(정적 공생성, 동적 공생성)을 실제 예시 하나로 구분하라.
  3. 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.

부록 A. 쉬운 용어 사전

한글 용어 원문 영문명 아주 쉬운 뜻 이 장에서 나온 위치
정적 공생성 Static Connascence 코드를 읽기만 해도 함께 바뀌어야 함이 보이는 관계. 부록 B와 본문 예시
동적 공생성 Dynamic Connascence 실행하거나 운영해 봐야 함께 바뀌어야 함이 드러나는 관계. 부록 B와 본문 예시
이름 공생성 Connascence of Name (CoN) 같은 이름 규칙을 공유하기 때문에 생기는 묶임. 부록 B와 본문 예시
의미 공생성 Connascence of Meaning (CoM) 같은 값을 같은 뜻으로 해석해야 해서 생기는 묶임. 부록 B와 본문 예시

부록 B. 헷갈리는 개념 비교표

A B 구분 포인트
정적 공생성 동적 공생성 정적은 코드만 봐도 보이고, 동적은 실행해야 드러난다.
이름 공생성 의미 공생성 이름은 같은 이름 약속, 의미는 같은 해석 약속에 묶인다.

부록 C. 더 읽을 자료

  • 이 장의 더 해보기 섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다.
  • 같은 책의 0장 한눈에 보기 — 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다.
  • 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
  • 이 장의 flashcards.json — 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.

부록 D. 연습문제 풀이

  1. 설명 예시. 함께 태어난 부품들는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다.
  2. 구분 예시. 두 개념(정적 공생성, 동적 공생성)의 차이는 이렇게 잡으면 된다. 정적은 코드만 봐도 보이고, 동적은 실행해야 드러난다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다.
  3. 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플